iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0

本篇階段:Prj#5 股票分析

使用介面:Claude Code(via VS Code)


前情

前四天在弄個人網站,Day 23 收尾之後那個專案就先告一段落。接下來五天換一個題目:把台股與美股的大盤、ETF 跟一堆總體經濟指標,做成一套每天自己會跑的東西。

會用到的帳號跟金鑰是在Day 12 那天就先去把該辦的辦掉了(現在手上應該會有帳號和金鑰了):永豐金證券開戶拿到帳號密碼、FRED 的金鑰、GitHub 帳號。當時寫「約十天後會用到」,就是現在。

第一天就有一件有一個撞到 AI 邊界的狀況,今年 (2026年) SPACE X 上市 (代號: SPCX),但一開始 Claude Code 很篤定回我:這個代號有問題,SpaceX 沒有上市。實際上 SpaceX 在 2026 年 6 月 12 日已經正式在 Nasdaq 掛牌了。是因為模型的知識截止日,造成在沒有查證的情況下,就直接回答未上市,在它訓練資料裡的世界,SpaceX 確實還沒上市。

救回來的方法是換一種問法:不要罵它「你搞錯了在幹嘛?」(而且有一派研究認為,在訓練這些模型的資料中,大型語言模型也會學到人類的情緒用詞,且它們會發現這些情緒詞出現時,產生犯錯的機率會增加),因此改成直接叫它去查、去抓 SPCX 的日線回來看,它抓回的第一根是 2026-06-12。

這點順帶暴露了另一件事:一年的日線大約是 250 根,這檔只有五分之一,凡是要一整年才算得出來的東西,在它身上都不成立,而這個邊界狀況後面幾天會反覆出現。

所以這件事定調了這個專案所有的做法:任何會被寫進結論的東西,都要去查、去跑一次,不要靠回想。 模型的回想有截止日! 就不得不說自己的回想也有,甚至是動態的(?)


重要說明:本文所有數字與敘述僅為個人技術實作紀錄,不構成投資建議


1. 為什麼另開兩個 repo,而不是長在個人網站裡

前四天才把個人網站重整過,最順手的做法當然是在那個 repo 裡開一個資料夾。我和 Claude 討論了以後,決定要分開,有兩個理由如下。

一是 commit 歷史。這套東西每天產資料、每天 push,一年下來三百多筆「更新今日資料」混在網站的改版紀錄裡,之後想找「我什麼時候改了導覽列」會很痛苦。二是這個 Side Project 不該害到我的個人網站,甚至個人網站的建置流程我三天前才調好,如果這個腳本掛了讓整個個人網站死掉,這個代價完全不成比例。

所以我開了兩個新的 repo:

  • 挑戰內容的那個:世界總經、台灣總經、台股大盤與 ETF、美股大盤與 ETF,這個專案寫的就是它。
  • 個股私人研究的那個:我自己另外想看的權值股數據,因為法規的關係只能自己私下看。

兩個都是 public 的原因不是想公開,是 GitHub Pages 的免費方案只支援 public repo,而內容目前靠頁面加密擋住。

有一條線在這裡就要畫清楚:**挑戰內容這一側從頭到尾不產生買賣建議。**例如 0050 跟 SPY 是具名的可交易標的,對它們給進出建議落在投顧業務的範圍。所以這條線畫在內容本身,評分數字可以有、分項可以有、機制怎麼做的可以講;把分數翻譯成動作的那一句,不生成。


2. 標的怎麼收斂

2.1 挑戰內容:每個市場「指數 + 兩檔 ETF」

最後留六個:

市場 指數(不可交易) 追蹤它的 ETF
台股 ^TWII 加權指數 0050.TW006208.TW
美股 ^GSPC S&P 500 SPYVOO

取「指數 + 兩檔」是為了讓後面幾天有兩組對照可看:大盤指數跟這兩檔 ETF 差在哪,以及兩檔 ETF 彼此又差在哪。這兩個問題個股沒有。

選法的部分,我要觀測的大盤標的就是加權指數 ^TWII,它涵蓋整個上市市場,是這套東西裡「台股今天怎麼樣」的定義。而 0050006208 追蹤的是臺灣 50 指數,只有五十檔,跟加權指數不是同一個籃子。

所以指數跟 ETF 之間的落差不是誤差,是我本來就要看的東西:一邊是整個市場,一邊是可以真的買到的五十檔,兩者分岔到什麼程度,本身就是一個讀數。這件事每次跑對照分析,第一行就會把「這兩個籃子不一樣」印出來(其實是怕跑久了會忘記)。

順帶釐清一個容易混的後綴。台股代號在 yfinance 有兩種尾巴:.TW上市.TWO上櫃。0050 是上市的,所以 0050.TW 抓得到、0050.TWO 回空;拿一檔真正的上櫃股去試(例如環球晶 6488.TWO)就抓得到。

2.2 權值股的名單從哪裡查

個股那一側的名單不從記憶來,也不問模型(SPCX 那件事就是教訓)。台股的權值股去期交所的問答頁查,那裡有台股期貨成分股的權重排序。美股的則看 TradingView 的大型股清單,按市值排。權值股之所以重要,是因為指數按市值加權,前幾大權值股動一點點,指數就跟著動,這也是為什麼「講 0050 幾乎等於在講台積電」。


3. 總經指標選了哪些

3.1 名單

先說明,黃金、原油、比特幣看起來都很有存在感,但沒把握它們跟台股大盤的關係穩定到可以編進分數,因此先分開看。

世界層(十一項,進評分)

指標 頻率 來源
VIX 恐慌指數 每日 yfinance
美元指數 DXY 每日 yfinance
美債 10 年殖利率 每日 yfinance
美債 10Y-2Y 利差 每日 FRED
美國 CPI 每月 FRED
美國失業率 每月 FRED
費城 Fed 製造業指數 每月 FRED
Fed 利率上限 不定期 FRED
美國初領失業金 每週 FRED
美國非農就業 每月 FRED
美國原油庫存 每週 EIA 每週石油狀況報告

台灣層(六項,進評分)

指標 頻率 來源
USD/TWD 每日 yfinance
費城半導體 SOX 每日 yfinance
台灣景氣對策信號 每月 國發會
台灣外銷訂單動向指數 每月 國發會
台灣經濟成長率 YoY 每季 主計總處
台灣央行重貼現率 不定期 央行

只顯示不評分(七項):黃金、WTI 原油、S&P 500、EUR/USD、USD/JPY、USD/CNY、比特幣。

合計二十四項,十七項進評分。

3.2 Claude 想刪四項,我留下三項

收斂名單這件事我交給 Claude 做,它提了四項要刪,理由各自成立:

  • 初領失業金:週頻,跟月頻的失業率重複
  • 非農就業:跟失業率高度共線
  • EIA 原油庫存:需要另一把金鑰,而且對評分貢獻低
  • S&P 500:它是被解釋的對象,不是解釋變數

第四項我同意,也就這一項真的刪掉了。拿大盤指數去解釋大盤,等於把答案抄進題目裡。

前三項我要求留下來,理由分兩種。

初領失業金與非農就業,Claude 那個「共線」的說法確實是一個學派的看法,但不是唯一的。另一派主張三個要配合看,因為它們量的是勞動市場的不同切面:

指標 量的是 頻率
失業率 存量 — 現在有多少人沒工作
初領失業金 邊際流量 — 這週新增多少人請領
非農就業 淨增減 — 這個月整體多了或少了多少職位

只留存量會漏掉轉折。失業率是落後指標,它動的時候事情已經發生一陣子了;初領是每週更新的,轉折會先出現在那裡。

EIA 原油庫存則是時空背景的問題。2026 年 9 月戰爭正在打,庫存是供給面的直接讀數,跟油價講的不是同一件事:油價會被預期、投機、金融面推動,庫存是實際有多少桶在儲槽裡。

這一段對我來說是個最典型的協作樣貌:Claude 給的四個理由沒有一個是錯的,但其中三個把「一個學派的看法」或「一條路的限制」當成了唯一解。 判斷的品質不在有沒有理由,在那個理由涵蓋了多少種可能。

把美國的費城半導體指數放進台灣層是 Claude 的判斷,理由是台股半導體占比極高。這一項我同意,但它同樣是可以被反駁的:SOX 成分股是美國上市公司,匯率、利率、資金面都跟台灣不同。

順帶一提 FRED 的金鑰。它其實有一個 fredgraph.csv 免金鑰端點,接上了官方 API 後額度會從 30 變成 120 req/min,但程式其實兩條路都留著:找得到金鑰走官方 API,找不到就退回 CSV 端點,沒有金鑰整套照樣跑,只是慢一點。


4. 張、股、口

這一節看起來最瑣碎,卻是需要提防的地方,因為這裡可能遇到一個 bug:標題寫「張」,資料其實是「股」。

台股講張、美股講股,一張一千股。而同一天、同樣來自證交所的兩份盤後資料,單位還不一樣:

  • 三大法人買賣超(T86):
  • 融資融券餘額(MI_MARGN):
  • 台指期未平倉(期交所):

三種單位擠在同一套系統裡,而且互不換算:一口台指期不是一千股的任何東西。

Claude 定的規矩是:內部一律存「股」,只在顯示層換算成「張」,而且換算只有一個入口。除此之外,每一個欄位名都自己帶單位字尾:_shares_lots_contracts_pct。這樣看到 margin_lots 就知道它是張。這條命名規矩也有一個測試守著,它會檢查每一個欄位名是不是都帶了單位字尾。


5. Shioaji 跟 yfinance 對得起來嗎

台股的價格同時用兩個來源抓:永豐金的 Shioaji,跟 yfinance。有衝突以 Shioaji 為主,因為它是券商的正式行情源,而 yfinance 抓的是 Yahoo 的前端端點,本來就沒有任何保證。

實際比對下來的結果比我預期乾淨:開高低收四欄完全對得上。

唯一穩定重現的衝突是 006208.TW 的成交量,兩邊每天都差大約 1.1%:Shioaji 報的是整千的數字(例如 1,941,000),yfinance 報的是實際股數(1,962,986)。差異的方向跟幅度每天都一樣,那就不是資料品質問題,是報法不同。

但一開始比出來的不是這樣。第一次跑,衝突有 26 筆,其中 21 筆全都來自 ^TWII

原因是加權指數的「成交量」。Shioaji 那邊報 9,256,323,000,yfinance 報 4,388,800,差了大約兩千倍,而同一天的價格四欄完全一致。

差的不是資料,是定義。個股的成交量有一個明確的東西在數:這檔股票今天換手了幾股。指數沒有那個東西可以數。加權指數不是一檔可以買賣的標的,它的「成交量」是把成分股的量用某種方式加總出來的,而兩家的加總方式顯然不同。

所以規則改成:指數不參與成交量的跨來源比對,也不套張換股的一千倍換算。 改完之後衝突從 26 筆掉到 5 筆,而那 5 筆是真的。這是指數與個股必須分開處理的第一個地方,後面還會再遇到。

這個修正的意義不在少了 21 筆,而在於:比對機制的用途是「筆數突然變多本身就是警報」,而那個前提是平常的筆數要有意義。天天有 21 筆假衝突,警報就等於沒有。


6. 比對、回看、重抓

價格序列還有一個更麻煩的性質:來源會回頭改寫歷史,而且改了不一定會說。

0050.TWStock Splits 欄位是空的,但三年前的收盤價顯示 31.69,當時實際價位在 126 上下。序列明顯被調整過,沒有任何欄位去說明這件事。

既然來源會偷偷改,就得先把「發現它改了」跟「把它改回來」分成兩件事。三個詞,意思不能混用:

做什麼 何時做 會不會改資料
比對 拿今天抓回來的,跟本機已存的同一批日期逐格比 每天,成本零 不改,只記旗標
回看 往回抓漏掉或從未抓過的日期 固定小窗或手動 補進缺的列,不動既有列
重抓 把某一檔的完整歷史整條抓下來覆寫 只手動觸發 覆寫整條

比對是煙霧偵測器、回看是補漏、重抓是修復。自動化只做第一件。

比對是零成本的:yfinance 一次 history() 就是一個請求,抓五天跟抓一天同樣一次。而分割或除權息會把整條歷史乘上同一個常數,所以最新五根對不上就足以知道發生了。判斷的是「有沒有變」,不是「變了幾天」。

自動修復一律不做。:比對函式回傳的型別刻意沒有「修正後的資料」這個欄位,想自動修都沒地方放,而且有測試守著它不准長出來。

價格因此存兩份:一份是當天抓到什麼就存什麼、只增不改的稽核軌跡;另一份是最新完整序列,允許被覆寫,拿來計算。只留一份「最新」的話,就永遠證明不了它改過。


7. 排程放本機

7.1 為什麼不是 Actions cron

GitHub Actions 有 cron,看起來是免費又不用開機的選擇。還是決定放本機,用 Windows 工作排程器。

主要理由是金鑰全留本機,CI 就完全不需要碰任何祕密。所以分工是:**本機抓、本機算、本機加密,Actions 只負責把推上去的東西部署出去。**這個分工有一個測試在守:掃過每一個 workflow 檔,出現 secrets. 就讓測試失敗。

代價很直接:筆電得開機。台股 18:00、美股 09:00 各排一個任務,兩個都有「錯過時間後盡快啟動」之機制。

例如早上 09:00 那個時間點筆電還在睡,在 17:54 醒來,那可能 17:58:25 美股那個任務自動跑起來,回傳碼 0,執行紀錄多出一筆。而且那一筆抓回來的美股筆數跟前一天一模一樣,因為 9 月 7 日是美國勞動節,NYSE 沒開盤。「資料沒變就不更新」這件事,跟補跑在同一次執行裡一起被驗到了。

7.2 節流跟額度

節流第一天就寫進資料源那一層,不放在呼叫端。每個來源自己持有一個節流器,任何呼叫路徑都繞不過去,呼叫的人不必記得 sleep,也不該有辦法跳過。

規矩大致是:FRED 固定一秒一次(只用官方額度的一半);yfinance 批次之間 sleep 一秒並禁用多執行緒;證交所跟期交所至少 0.6 秒,以「資料集加日期」為鍵整檔快取。每次跑完把當天各來源打了幾次請求記進執行紀錄。

Shioaji 那一格特別重要,因為它超流量的後果不是報錯,是行情查詢直接回空值,程式會以為「那天沒資料」。不把剩餘流量記下來,就不知道離上限還有多遠。

7.3 盤後資料公布時間

實測時間:

資料 大約幾點公布 18:00 抓得到嗎
台股日 K(yfinance) 收盤後很快 抓得到
台指期未平倉、put/call(期交所) 盤後不久 抓得到
三大法人買賣超(證交所 T86) 約 17:30 抓得到
融資融券餘額(證交所 MI_MARGN) 約 21:30,可能更晚 抓不到

18:00 那次排程確實把當天三檔台股的收盤都抓回來了,T86 也有 17,347 檔,只有融資融券「沒有符合條件的資料」。

所以籌碼面拆成兩班:18:00 抓三大法人跟期交所那兩項,22:30 再補融資融券。18:00 那一班根本不去問融資融券,不是問了失敗。少打一次註定落空的請求,執行紀錄也不會每天多一筆看起來像故障的失敗。

這件事還逼出頁面上一個小設計。同一天、同一個交易所的兩份盤後資料,公布時間就差了四個小時,總經序列更是散落在七八個不同的日期。所以頁面上的時間分成兩層:標頭放一個「最後一次抓取」,明寫它是管線執行的時間;每一列的資料日期各自標在該列。

寫成一行「資料更新時間」最省事,但那等於宣稱所有數字都是那一刻的,而 CPI 停在七月、央行停在 2024 年。省下來的那一行,代價是整頁的可信度。


小結

今天大部分的時間不是花在寫抓資料的程式上,是花在把幾個「之後一定會混淆的東西」先釘死:張跟股跟口誰是誰、比對跟回看跟重抓各做什麼、.TW.TWO 差在哪。

釘的方式盡量不是註解,是測試:畫面層不准 import 儲存層、欄位名一定要帶單位、比對函式不准長出修改資料的能力。

而今天最值得記下來的還是開場那件事。模型很有自信地告訴我 SpaceX 沒上市,那個自信跟它的正確性完全無關,它只是誠實地回報了一個截止在過去的世界。去查、去跑一次,比回想可靠。

參考資料

資料來源

其他參考


註一:文中的資料差異、公布時間、排程補跑都是 2026 年 9 月 6 日至 7 日這兩天的單次實測,屬於定性觀察,不是統計證據;外部資料源的行為會隨時間改變。

註二:排程補跑那一次是實際發生的觀察,不是受控實驗,沒有調整系統時間、也沒有刻意關機,只是剛好機器在排定時間睡著了。

註三:各項盤後資料的公布時間(三大法人約 17:30、融資融券約 21:30)是 9 月 7 日單日觀察加上作者既有經驗,證交所沒有公布保證時間,實際可能逐日浮動


重要說明:本文所有數字與敘述僅為個人技術實作紀錄,不構成投資建議


上一篇
Day 23|Claude 幫我把個人網頁更新了,順便把履歷照片藏在刮刮樂底下
下一篇
Day 25|當 Claude 開始幫我看股市:24 個總經指標,卻先敗在「資料到底是哪一天的?」
系列文
三個介面,一套工作流?30 天 Claude 跨領域實戰:從 claude.ai、Claude Desktop 到 Claude Code25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言